package com.mazaiting.classfile.attr.entity;

import com.mazaiting.classfile.ClassReader;
import com.mazaiting.classfile.interfaces.Reader;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * 栈帧实体
 *
 * @author mazaiting
 * @since 2021/8/12
 */
@Getter
@Slf4j
public abstract class StackMapFrameEntity implements Reader {
    /**
     * 帧类型 u1 无符号
     * Tags in the range [128-246] are reserved for future use.
     */
    protected final short frameType;
    /**
     * 偏移量
     */
    protected int offsetDelta;
    /**
     * 总偏移量
     */
    protected int offset;

    public StackMapFrameEntity(short frameType, int offset) {
        this.frameType = frameType;
        this.offset = offset;
    }

    /**
     * 静态读取栈帧
     *
     * @param reader    类读取器
     * @param frameType 帧类型
     * @param offset    总偏移量
     * @return 栈帧实体
     */
    public static StackMapFrameEntity read(ClassReader reader, short frameType, int offset) {
        StackMapFrameEntity entity;
        if (frameType <= 63) {
            entity = new SameFrame(frameType, offset);
        } else if (frameType <= 127) {
            entity = new SameLocals1StackItemFrame(frameType, offset);
        } else if (frameType < 246) {
            throw new RuntimeException("unknown frame_type " + frameType);
        } else if (frameType == 247) {
            entity = new SameLocals1StackItemFrameExtended(frameType, offset);
        } else if (frameType <= 250) {
            entity = new ChopFrame(frameType, offset);
        } else if (frameType == 251) {
            entity = new SameFrameExtended(frameType, offset);
        } else {
            entity = frameType <= 254 ? new AppendFrame(frameType, offset) : new FullFrame(frameType, offset);
        }

        entity.readInfo(reader);
        return entity;
    }

    /**
     * 添加偏移量
     */
    protected void addOffset() {
        this.offset = offset + (offsetDelta == 0 ? frameType : offsetDelta);
    }

    @Override
    public String toString() {
        return "StackMapFrameEntity{" +
                "frameType=" + frameType +
                ", offsetDelta=" + offsetDelta +
                ", offset=" + offset +
                '}';
    }

    private static class SameFrame extends StackMapFrameEntity {

        public SameFrame(short frameType, int offsetDelta) {
            super(frameType, offsetDelta);
        }

        @Override
        public void readInfo(ClassReader reader) {
            addOffset();
        }

        @Override
        public String toString() {
            return "SameFrame{" +
                    "frameType=" + frameType +
                    ", offsetDelta=" + offsetDelta +
                    ", offset=" + offset +
                    '}';
        }
    }

    @Getter
    private static class SameLocals1StackItemFrame extends StackMapFrameEntity {
        /**
         * 栈
         */
        private final VerificationTypeInfoEntity[] stack = new VerificationTypeInfoEntity[1];

        public SameLocals1StackItemFrame(short frameType, int offsetDelta) {
            super(frameType, offsetDelta);
        }

        @Override
        public void readInfo(ClassReader reader) {
            addOffset();
            this.stack[0] = VerificationTypeInfoEntity.read(reader);
        }

        @Override
        public String toString() {
            return "SameLocals1StackItemFrame{" +
                    "frameType=" + frameType +
                    ", offsetDelta=" + offsetDelta +
                    ", offset=" + offset +
                    ", stack=" + Arrays.toString(stack) +
                    '}';
        }
    }

    @Getter
    private static class SameLocals1StackItemFrameExtended extends StackMapFrameEntity {

        /**
         * 验证信息实体
         */
        private final VerificationTypeInfoEntity[] stack = new VerificationTypeInfoEntity[1];

        public SameLocals1StackItemFrameExtended(short frameType, int offsetDelta) {
            super(frameType, offsetDelta);
        }

        @Override
        public void readInfo(ClassReader reader) {
            this.offsetDelta = reader.readU2ToUnsignedShort();
            addOffset();
            this.stack[0] = VerificationTypeInfoEntity.read(reader);
        }

        @Override
        public String toString() {
            return "SameLocals1StackItemFrameExtended{" +
                    "frameType=" + frameType +
                    ", offsetDelta=" + offsetDelta +
                    ", offset=" + offset +
                    ", stack=" + Arrays.toString(stack) +
                    '}';
        }
    }

    @Getter
    private static class ChopFrame extends StackMapFrameEntity {

        public ChopFrame(short frameType, int offset) {
            super(frameType, offset);
        }

        @Override
        public void readInfo(ClassReader reader) {
            this.offsetDelta = reader.readU2ToUnsignedShort();
            addOffset();
        }

        @Override
        public String toString() {
            return "ChopFrame{" +
                    "frameType=" + frameType +
                    ", offsetDelta=" + offsetDelta +
                    ", offset=" + offset +
                    '}';
        }
    }

    @Getter
    private static class SameFrameExtended extends StackMapFrameEntity {

        public SameFrameExtended(short frameType, int offset) {
            super(frameType, offset);
        }

        @Override
        public void readInfo(ClassReader reader) {
            this.offsetDelta = reader.readU2ToUnsignedShort();
            addOffset();
        }

        @Override
        public String toString() {
            return "SameFrameExtended{" +
                    "frameType=" + frameType +
                    ", offsetDelta=" + offsetDelta +
                    ", offset=" + offset +
                    '}';
        }
    }

    @Getter
    private static class AppendFrame extends StackMapFrameEntity {
        /**
         * 验证信息实体
         */
        private VerificationTypeInfoEntity[] locals;

        public AppendFrame(short frameType, int offset) {
            super(frameType, offset);
        }

        @Override
        public void readInfo(ClassReader reader) {
            this.offsetDelta = reader.readU2ToUnsignedShort();
            addOffset();
            this.locals = new VerificationTypeInfoEntity[frameType - 251];
            for (int i = 0; i < locals.length; i++) {
                this.locals[i] = VerificationTypeInfoEntity.read(reader);
            }
        }

        @Override
        public String toString() {
            return "AppendFrame{" +
                    "frameType=" + frameType +
                    ", offsetDelta=" + offsetDelta +
                    ", offset=" + offset +
                    ", locals=" + Arrays.toString(locals) +
                    '}';
        }
    }

    @Getter
    private static class FullFrame extends StackMapFrameEntity {
        /**
         * 本地变量数 无符号 u2
         */
        private int numberOfLocals;
        /**
         * 本地表
         */
        private VerificationTypeInfoEntity[] locals;
        /**
         * 栈条目数 无符号 u2
         */
        private int numberOfStackItems;
        /**
         * 栈
         */
        private VerificationTypeInfoEntity[] stack;

        public FullFrame(short frameType, int offset) {
            super(frameType, offset);
        }

        @Override
        public void readInfo(ClassReader reader) {
            this.offsetDelta = reader.readU2ToUnsignedShort();
            addOffset();
            this.numberOfLocals = reader.readU2ToUnsignedShort();
            this.locals = new VerificationTypeInfoEntity[numberOfLocals];
            for (int i = 0; i < numberOfLocals; i++) {
                this.locals[i] = VerificationTypeInfoEntity.read(reader);
            }
            this.numberOfStackItems = reader.readU2ToUnsignedShort();
            this.stack = new VerificationTypeInfoEntity[numberOfStackItems];
            for (int i = 0; i < numberOfStackItems; i++) {
                this.stack[i] = VerificationTypeInfoEntity.read(reader);
            }
        }

        @Override
        public String toString() {
            return "FullFrame{" +
                    "frameType=" + frameType +
                    ", offsetDelta=" + offsetDelta +
                    ", offset=" + offset +
                    ", numberOfLocals=" + numberOfLocals +
                    ", locals=" + Arrays.toString(locals) +
                    ", numberOfStackItems=" + numberOfStackItems +
                    ", stack=" + Arrays.toString(stack) +
                    '}';
        }
    }


}


















